broadwayd.c \
broadway-server.h \
broadway-server.c \
+ broadway-buffer.c \
+ broadway-buffer.h \
broadway-output.h \
broadway-output.c
--- /dev/null
+#include "config.h"
+
+#include "broadway-buffer.h"
+
+#include <string.h>
+
+/* This code is based on some code from weston with this license:
+ *
+ * Copyright © 2012 Intel Corporation
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and
+ * its documentation for any purpose is hereby granted without fee, provided
+ * that the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation, and that the name of the copyright holders not be used in
+ * advertising or publicity pertaining to distribution of the software
+ * without specific, written prior permission. The copyright holders make
+ * no representations about the suitability of this software for any
+ * purpose. It is provided "as is" without express or implied warranty.
+ *
+ * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
+ * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
+ * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
+ * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+struct entry {
+ int count;
+ int matches;
+ guint32 hash;
+ int x, y;
+ int index;
+};
+
+struct _BroadwayBuffer {
+ guint8 *data;
+ struct entry *table;
+ int width, height, stride;
+ int encoded;
+ int block_stride, length, block_count, shift;
+ int stats[5];
+ int clashes;
+};
+
+static const guint32 prime = 0x1f821e2d;
+static const guint32 end_prime = 0xf907ec81; /* prime^block_size */
+#if 0
+static const guint32 vprime = 0x0137b89d;
+static const guint32 end_vprime = 0xaea9a281; /* vprime^block_size */
+#else
+static const guint32 vprime = 0xf907ec81;
+static const guint32 end_vprime = 0xcdb99001; /* vprime^block_size */
+#endif
+static const guint32 step = 0x0ac93019;
+static const int block_size = 32, block_mask = 31;
+
+static gboolean
+verify_block_match (BroadwayBuffer *buffer, int x, int y,
+ BroadwayBuffer *prev, struct entry *entry)
+{
+ int i;
+ void *old, *match;
+ int w1, w2, h1, h2;
+
+ w1 = block_size;
+ if (x + block_size > buffer->width)
+ w1 = buffer->width - x;
+
+ h1 = block_size;
+ if (y + block_size > buffer->height)
+ h1 = buffer->height - y;
+
+ w2 = block_size;
+ if (entry->x + block_size > prev->width)
+ w2 = prev->width - entry->x;
+
+ h2 = block_size;
+ if (entry->y + block_size > prev->height)
+ h2 = prev->height - entry->y;
+
+ if (w1 != w2 || h1 != h2)
+ return FALSE;
+
+ for (i = 0; i < h1; i++)
+ {
+ match = buffer->data + (y + i) * buffer->stride + x * 4;
+ old = prev->data + (entry->y + i) * prev->stride + entry->x * 4;
+ if (memcmp (match, old, w1 * 4) != 0)
+ {
+ buffer->clashes++;
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+static void
+insert_block (BroadwayBuffer *buffer, guint32 h, int x, int y)
+{
+ struct entry *entry;
+ int i;
+ guint32 collision = 0;
+
+ entry = &buffer->table[h >> buffer->shift];
+ for (i = step; entry->count > 0 && entry->hash != h; i += step)
+ {
+ entry = &buffer->table[(h + i) >> buffer->shift];
+ collision++;
+ }
+
+ entry->hash = h;
+ entry->count++;
+ entry->x = x;
+ entry->y = y;
+ entry->index = (buffer->block_stride * y + x) / block_size;
+
+ if (collision > G_N_ELEMENTS (buffer->stats) - 1)
+ collision = G_N_ELEMENTS (buffer->stats) - 1;
+ buffer->stats[collision]++;
+}
+
+static struct entry *
+lookup_block (BroadwayBuffer *prev, guint32 h)
+{
+ guint32 i;
+ struct entry *entry;
+ int shift = prev->shift;
+
+ for (i = h;
+ entry = &prev->table[i >> shift], entry->count > 0;
+ i += step)
+ {
+ if (entry->hash == h)
+ return entry;
+ }
+
+ return NULL;
+}
+
+struct encoder {
+ guint32 color;
+ guint32 color_run;
+ guint32 delta;
+ guint32 delta_run;
+ GString *dest;
+ int bytes;
+};
+
+/* Encoding:
+ *
+ * - all 1 pixel colors are encoded literally
+ *
+ * - We don't need to support colors with alpha 0 and non-zero
+ * color components, as they mean the same on the canvas anyway.
+ * So we use these as special codes:
+ *
+ * - 0x00 00 00 00 : one alpha 0 pixel
+ * - 0xaa rr gg bb : one color pixel, alpha > 0
+ * - 0x00 1x xx xx : delta 0 run, x is length, (20 bits)
+ * - 0x00 2x xx xx 0x xxxx yyyy: block ref, block number x (20 bits) at x, y
+ * - 0x00 3x xx xx 0xaarrggbb : solid color run, length x
+ * - 0x00 4x xx xx 0xaarrggbb : delta run, length x
+ *
+ */
+
+static void
+emit (struct encoder *encoder, guint32 symbol)
+{
+ g_string_append_len (encoder->dest, (char *)&symbol, sizeof (guint32));
+ encoder->bytes += sizeof (guint32);
+}
+
+static void
+encode_run (struct encoder *encoder)
+{
+ if (encoder->color_run == 0 && encoder->delta_run == 0)
+ return;
+
+ if (encoder->color_run >= encoder->delta_run)
+ {
+ if (encoder->color_run == 1)
+ emit (encoder, encoder->color);
+ else
+ {
+ emit (encoder, 0x00300000 | encoder->color_run);
+ emit (encoder, encoder->color);
+ }
+ }
+ else
+ {
+ if (encoder->delta == 0)
+ emit(encoder, 0x00100000 | encoder->delta_run);
+ else
+ {
+ emit(encoder, 0x00400000 | encoder->delta_run);
+ emit(encoder, encoder->delta);
+ }
+ }
+}
+
+static void
+encode_pixel (struct encoder *encoder, guint32 color, guint32 prev_color)
+{
+ guint32 delta = 0;
+ guint32 a, r, g, b;
+
+ if (color == prev_color)
+ delta = 0;
+ else if (prev_color == 0)
+ delta = color;
+ else
+ {
+ a = ((color & 0xff000000) - (prev_color & 0xff000000)) & 0xff000000;
+ r = ((color & 0x00ff0000) - (prev_color & 0x00ff0000)) & 0x00ff0000;
+ g = ((color & 0x0000ff00) - (prev_color & 0x0000ff00)) & 0x0000ff00;
+ b = ((color & 0x000000ff) - (prev_color & 0x000000ff)) & 0x000000ff;
+
+ delta = a | r | g | b;
+ }
+
+ if ((encoder->color != color &&
+ encoder->color_run > encoder->delta_run) ||
+
+ (encoder->delta != delta &&
+ encoder->delta_run > encoder->color_run) ||
+
+ (encoder->delta != delta && encoder->color != color))
+ {
+ encode_run (encoder);
+
+ encoder->color_run = 1;
+ encoder->color = color;
+ encoder->delta_run = 1;
+ encoder->delta = delta;
+ return;
+ }
+
+ if (encoder->color == color)
+ encoder->color_run++;
+ else
+ {
+ encoder->color_run = 1;
+ encoder->color = color;
+ }
+
+ if (encoder->delta == delta)
+ encoder->delta_run++;
+ else
+ {
+ encoder->delta_run = 1;
+ encoder->delta = delta;
+ }
+}
+
+void
+encoder_flush (struct encoder *encoder)
+{
+ encode_run (encoder);
+}
+
+
+static void
+encode_block (struct encoder *encoder, struct entry *entry, int x, int y)
+{
+ /* 0x00 2x xx xx 0x xxxx yyyy:
+ * block ref, block number x (20 bits) at x, y */
+
+ /* FIXME: Maybe don't encode pixels under blocks and just emit
+ * blocks at their position within the stream. */
+
+ emit (encoder, 0x00200000 | entry->index);
+ emit (encoder, (x << 16) | y);
+}
+
+void
+broadway_buffer_destroy (BroadwayBuffer *buffer)
+{
+ g_free (buffer->data);
+ g_free (buffer->table);
+ g_free (buffer);
+}
+
+int
+broadway_buffer_get_width (BroadwayBuffer *buffer)
+{
+ return buffer->width;
+}
+
+int
+broadway_buffer_get_height (BroadwayBuffer *buffer)
+{
+ return buffer->height;
+}
+
+static void
+unpremultiply_line (void *destp, void *srcp, int width)
+{
+ guint32 *src = srcp;
+ guint32 *dest = destp;
+ guint32 *end = src + width;
+ while (src < end)
+ {
+ guint32 pixel;
+ guint8 alpha, r, g, b;
+
+ pixel = *src++;
+
+ alpha = (pixel & 0xff000000) >> 24;
+
+ if (alpha == 0xff)
+ *dest++ = pixel;
+ else if (alpha == 0)
+ *dest++ = 0;
+ else
+ {
+ r = (((pixel & 0xff0000) >> 16) * 255 + alpha / 2) / alpha;
+ g = (((pixel & 0x00ff00) >> 8) * 255 + alpha / 2) / alpha;
+ b = (((pixel & 0x0000ff) >> 0) * 255 + alpha / 2) / alpha;
+ *dest++ = (guint32)alpha << 24 | (guint32)r << 16 | (guint32)g << 8 | (guint32)b;
+ }
+ }
+}
+
+BroadwayBuffer *
+broadway_buffer_create (int width, int height, guint8 *data, int stride)
+{
+ BroadwayBuffer *buffer;
+ int y, bits_required;
+
+ buffer = g_new0 (BroadwayBuffer, 1);
+ buffer->width = width;
+ buffer->stride = width * 4;
+ buffer->height = height;
+
+ buffer->block_stride = (width + block_size - 1) / block_size;
+ buffer->block_count =
+ buffer->block_stride * ((height + block_size - 1) / block_size);
+ bits_required = g_bit_storage (buffer->block_count * 4);
+ buffer->shift = 32 - bits_required;
+ buffer->length = 1 << bits_required;
+
+ buffer->table = g_malloc0 (buffer->length * sizeof buffer->table[0]);
+
+ memset (buffer->stats, 0, sizeof buffer->stats);
+ buffer->clashes = 0;
+
+ buffer->data = g_malloc (buffer->stride * height);
+
+ for (y = 0; y < height; y++)
+ unpremultiply_line (buffer->data + y * buffer->stride, data + y * stride, width);
+
+ return buffer;
+}
+
+void
+broadway_buffer_encode (BroadwayBuffer *buffer, BroadwayBuffer *prev, GString *dest)
+{
+ struct entry *entry;
+ int i, j, k;
+ int x0, x1, y0, y1;
+ guint32 *block_hashes;
+ guint32 hash, bottom_hash, h, *line, *bottom, *prev_line;
+ int width, height;
+ struct encoder encoder = { 0 };
+ int *skyline, skyline_pixels;
+ int matches;
+
+ width = buffer->width;
+ height = buffer->height;
+ x0 = 0;
+ x1 = width;
+ y0 = 0;
+ y1 = height;
+
+ skyline = g_malloc0 ((width + block_size) * sizeof skyline[0]);
+
+ block_hashes = g_malloc0 (width * sizeof block_hashes[0]);
+
+ matches = 0;
+ encoder.dest = dest;
+
+ // Calculate the block hashes for the first row
+ for (i = y0; i < MIN(y1, y0 + block_size); i++)
+ {
+ line = (guint32 *)(buffer->data + i * buffer->stride);
+ hash = 0;
+ for (j = x0; j < MIN(x1, x0 + block_size); j++)
+ hash = hash * prime + line[j];
+ for (j = j; j < x0 + block_size; j++)
+ hash = hash * prime;
+
+ for (j = x0; j < x1; j++)
+ {
+ block_hashes[j] = block_hashes[j] * vprime + hash;
+
+ hash = hash * prime - line[j] * end_prime;
+ if (j + block_size < width)
+ hash += line[j + block_size];
+ }
+ }
+ // Do the last rows if height < block_size
+ for (i = i; i < y0 + block_size; i++)
+ {
+ for (j = x0; j < x1; j++)
+ block_hashes[j] = block_hashes[j] * vprime;
+ }
+
+ for (i = y0; i < y1; i++)
+ {
+ line = (guint32 *) (buffer->data + i * buffer->stride);
+ bottom = (guint32 *) (buffer->data + (i + block_size) * buffer->stride);
+ bottom_hash = 0;
+ hash = 0;
+ skyline_pixels = 0;
+
+ if (prev && i < prev->height)
+ prev_line = (guint32 *) (prev->data + i * prev->stride);
+ else
+ prev_line = NULL;
+
+ for (j = x0; j < x0 + block_size; j++)
+ {
+ hash = hash * prime;
+ if (j < width)
+ hash += line[j];
+ if (i + block_size < height)
+ {
+ bottom_hash = bottom_hash * prime;
+ if (j < width)
+ bottom_hash += bottom[j];
+ }
+ if (i < skyline[j])
+ skyline_pixels = 0;
+ else
+ skyline_pixels++;
+ }
+
+ for (j = x0; j < x1; j++)
+ {
+ if (i < skyline[j])
+ encode_pixel (&encoder, line[j], line[j]);
+ else if (prev)
+ {
+ /* FIXME: Add back overlap exception
+ * for consecutive blocks */
+
+ h = block_hashes[j];
+ entry = lookup_block (prev, h);
+ if (entry && entry->count < 2 &&
+ skyline_pixels >= block_size &&
+ verify_block_match (buffer, j, i, prev, entry) &&
+ (entry->x != j || entry->y != i))
+ {
+ matches++;
+ encode_block (&encoder, entry, j, i);
+
+ for (k = 0; k < block_size; k++)
+ skyline[j + k] = i + block_size;
+
+ encode_pixel (&encoder, line[j], line[j]);
+ }
+ else
+ {
+ if (prev_line && j < prev->width)
+ encode_pixel (&encoder, line[j],
+ prev_line[j]);
+ else
+ encode_pixel (&encoder, line[j], 0);
+ }
+ }
+ else
+ encode_pixel (&encoder, line[j], 0);
+
+ if (i < skyline[j + block_size])
+ skyline_pixels = 0;
+ else
+ skyline_pixels++;
+
+ /* Insert block in hash table if we're on a
+ * grid point. */
+ if (((i | j) & block_mask) == 0 && !buffer->encoded)
+ insert_block (buffer, block_hashes[j], j, i);
+
+ /* Update sliding block hash */
+ block_hashes[j] =
+ block_hashes[j] * vprime + bottom_hash -
+ hash * end_vprime;
+
+ if (i + block_size < height)
+ {
+ bottom_hash = bottom_hash * prime - bottom[j] * end_prime;
+ if (j + block_size < width)
+ bottom_hash += bottom[j + block_size];
+ }
+ hash = hash * prime - line[j] * end_prime;
+ if (j + block_size < width)
+ hash += line[j + block_size] ;
+ }
+ }
+
+ encoder_flush (&encoder);
+
+#if 0
+ fprintf(stderr, "collision stats:");
+ for (i = 0; i < (int) G_N_ELEMENTS(buffer->stats); i++)
+ fprintf(stderr, "%c%d", i == 0 ? ' ' : '/', buffer->stats[i]);
+ fprintf(stderr, "\n");
+
+ fprintf(stderr, "%d / %d blocks (%d%%) matched, %d clashes\n",
+ matches, buffer->block_count,
+ 100 * matches / buffer->block_count, buffer->clashes);
+
+ fprintf(stderr, "output stream %d bytes, raw buffer %d bytes (%d%%)\n",
+ encoder.bytes, height * buffer->stride,
+ 100 * encoder.bytes / (height * buffer->stride));
+#endif
+
+ g_free (skyline);
+ g_free (block_hashes);
+
+ buffer->encoded = TRUE;
+}
--- /dev/null
+#ifndef __BROADWAY_BUFFER__
+#define __BROADWAY_BUFFER__
+
+#include "broadway-protocol.h"
+#include <glib-object.h>
+
+typedef struct _BroadwayBuffer BroadwayBuffer;
+
+BroadwayBuffer *broadway_buffer_create (int width,
+ int height,
+ guint8 *data,
+ int stride);
+void broadway_buffer_destroy (BroadwayBuffer *buffer);
+void broadway_buffer_encode (BroadwayBuffer *buffer,
+ BroadwayBuffer *prev,
+ GString *dest);
+int broadway_buffer_get_width (BroadwayBuffer *buffer);
+int broadway_buffer_get_height (BroadwayBuffer *buffer);
+
+#endif /* __BROADWAY_BUFFER__ */
#include "broadway-output.h"
-static cairo_status_t
-write_png_data (void *closure,
- const unsigned char *data,
- unsigned int data_len)
-{
- GString *buf = closure;
-
- g_string_append_len (buf, (char *)data, data_len);
-
- return CAIRO_STATUS_SUCCESS;
-}
-
-static void
-to_png_rgb (GString *buf, int w, int h, int byte_stride, guint32 *data)
-{
- cairo_surface_t *surface;
-
- surface = cairo_image_surface_create_for_data ((guchar *)data,
- CAIRO_FORMAT_RGB24, w, h, byte_stride);
-
- cairo_surface_write_to_png_stream (surface, write_png_data, buf);
- cairo_surface_destroy (surface);
-}
-
-static void
-to_png_rgba (GString *buf, int w, int h, int byte_stride, guint32 *data)
-{
- cairo_surface_t *surface;
-
- surface = cairo_image_surface_create_for_data ((guchar *)data,
- CAIRO_FORMAT_ARGB32, w, h, byte_stride);
-
- cairo_surface_write_to_png_stream (surface, write_png_data, buf);
- cairo_surface_destroy (surface);
-}
-
/************************************************************************
* Basic I/O primitives *
************************************************************************/
append_uint16 (output, parent_id);
}
-
void
-broadway_output_put_rgb (BroadwayOutput *output, int id, int x, int y,
- int w, int h, int byte_stride, void *data)
+broadway_output_put_buffer (BroadwayOutput *output,
+ int id,
+ BroadwayBuffer *prev_buffer,
+ BroadwayBuffer *buffer)
{
gsize size_start, image_start, len;
+ int w, h;
+
+ write_header (output, BROADWAY_OP_PUT_BUFFER);
- write_header (output, BROADWAY_OP_PUT_RGB);
+ w = broadway_buffer_get_width (buffer);
+ h = broadway_buffer_get_height (buffer);
append_uint16 (output, id);
- append_uint16 (output, x);
- append_uint16 (output, y);
+ append_uint16 (output, w);
+ append_uint16 (output, h);
size_start = output->buf->len;
append_uint32 (output, 0);
image_start = output->buf->len;
- to_png_rgb (output->buf, w, h, byte_stride, (guint32*)data);
-
+ broadway_buffer_encode (buffer, prev_buffer, output->buf);
len = output->buf->len - image_start;
overwrite_uint32 (output, size_start, len);
}
-
-typedef struct {
- int x1, y1;
- int x2, y2;
-} BroadwayBox;
-
-static int
-is_any_x_set (unsigned char *data,
- int box_x1, int box_x2,
- int x1, int x2, int y, int *x_set,
- int byte_stride)
-{
- int w ;
- guint32 *ptr;
-
- if (x1 < box_x1)
- x1 = box_x1;
-
- if (x2 > box_x2)
- x2 = box_x2;
-
- w = x2 - x1;
- if (w > 0)
- {
- ptr = (guint32 *)(data + y * byte_stride + x1 * 4);
- while (w-- > 0)
- {
- if (*ptr != 0)
- {
- if (x_set)
- *x_set = x1;
- return 1;
- }
- ptr++;
- x1++;
- }
- }
- return 0;
-}
-
-
-#define EXTEND_X_FUZZ 10
-#define EXTEND_Y_FUZZ 10
-
-static int
-extend_x_range (unsigned char *data,
- int box_x1, int box_y1,
- int box_x2, int box_y2,
- int *x1, int *x2, int y,
- int byte_stride)
-{
- int extended = 0;
- int new_x;
-
- while (is_any_x_set (data, box_x1, box_x2, *x1 - EXTEND_X_FUZZ, *x1, y, &new_x, byte_stride))
- {
- *x1 = new_x;
- extended = 1;
- }
-
- while (is_any_x_set (data, box_x1, box_x2, *x2, *x2 + EXTEND_X_FUZZ, y, &new_x, byte_stride))
- {
- *x2 = new_x + 1;
- extended = 1;
- }
-
- return extended;
-}
-
-static int
-extend_y_range (unsigned char *data,
- int box_x1, int box_y1,
- int box_x2, int box_y2,
- int x1, int x2, int *y,
- int byte_stride)
-{
- int extended = 0;
- int found_set;
- int yy, y2;
-
- while (*y < box_y2)
- {
- found_set = 0;
-
- y2 = *y + EXTEND_Y_FUZZ;
- if (y2 > box_y2)
- y2 = box_y2;
-
- for (yy = y2; yy > *y + 1; yy--)
- {
- if (is_any_x_set (data, box_x1, box_x2, x1, x2, yy - 1, NULL, byte_stride))
- {
- found_set = 1;
- break;
- }
- }
- if (!found_set)
- break;
- *y = yy;
- extended = 1;
- }
-
- return extended;
-}
-
-
-static void
-rgba_find_rects_extents (unsigned char *data,
- int box_x1, int box_y1,
- int box_x2, int box_y2,
- int x, int y,
- BroadwayBox *rect,
- int byte_stride)
-{
- int x1, x2, y1, y2, yy;
- int extended;
-
- x1 = x;
- x2 = x + 1;
- y1 = y;
- y2 = y + 1;
-
- do
- {
- /* Expand maximally for all known rows */
- do
- {
- extended = 0;
-
- for (yy = y1; yy < y2; yy++)
- extended |= extend_x_range (data,
- box_x1, box_y1,
- box_x2, box_y2,
- &x1, &x2, yy,
- byte_stride);
- }
- while (extended);
- }
- while (extend_y_range(data,
- box_x1, box_y1,
- box_x2, box_y2,
- x1, x2, &y2,
- byte_stride));
-
- rect->x1 = x1;
- rect->x2 = x2;
- rect->y1 = y1;
- rect->y2 = y2;
-}
-
-static void
-rgba_find_rects_sub (unsigned char *data,
- int box_x1, int box_y1,
- int box_x2, int box_y2,
- int byte_stride,
- BroadwayBox **rects,
- int *n_rects, int *alloc_rects)
-{
- guint32 *line;
- BroadwayBox rect;
- int x, y;
-
- if (box_x1 == box_x2 || box_y1 == box_y2)
- return;
-
- for (y = box_y1; y < box_y2; y++)
- {
- line = (guint32 *)(data + y * byte_stride + box_x1 * 4);
-
- for (x = box_x1; x < box_x2; x++)
- {
- if (*line != 0)
- {
- rgba_find_rects_extents (data,
- box_x1, box_y1, box_x2, box_y2,
- x, y, &rect, byte_stride);
- if (*n_rects == *alloc_rects)
- {
- (*alloc_rects) *= 2;
- *rects = g_renew (BroadwayBox, *rects, *alloc_rects);
- }
- (*rects)[*n_rects] = rect;
- (*n_rects)++;
- rgba_find_rects_sub (data,
- box_x1, rect.y1,
- rect.x1, rect.y2,
- byte_stride,
- rects, n_rects, alloc_rects);
- rgba_find_rects_sub (data,
- rect.x2, rect.y1,
- box_x2, rect.y2,
- byte_stride,
- rects, n_rects, alloc_rects);
- rgba_find_rects_sub (data,
- box_x1, rect.y2,
- box_x2, box_y2,
- byte_stride,
- rects, n_rects, alloc_rects);
- return;
- }
- line++;
- }
- }
-}
-
-static BroadwayBox *
-rgba_find_rects (unsigned char *data,
- int w, int h, int byte_stride,
- int *n_rects)
-{
- BroadwayBox *rects;
- int alloc_rects;
-
- alloc_rects = 20;
- rects = g_new (BroadwayBox, alloc_rects);
-
- *n_rects = 0;
- rgba_find_rects_sub (data,
- 0, 0, w, h, byte_stride,
- &rects, n_rects, &alloc_rects);
-
- return rects;
-}
-
-void
-broadway_output_put_rgba (BroadwayOutput *output, int id, int x, int y,
- int w, int h, int byte_stride, void *data)
-{
- BroadwayBox *rects;
- int i, n_rects;
- gsize size_start, image_start, len;
-
- rects = rgba_find_rects (data, w, h, byte_stride, &n_rects);
-
- for (i = 0; i < n_rects; i++)
- {
- guint8 *subdata;
-
- write_header (output, BROADWAY_OP_PUT_RGB);
- append_uint16 (output, id);
- append_uint16 (output, x + rects[i].x1);
- append_uint16 (output, y + rects[i].y1);
-
- size_start = output->buf->len;
- append_uint32 (output, 0);
-
- image_start = output->buf->len;
-
- subdata = (guint8 *)data + rects[i].x1 * 4 + rects[i].y1 * byte_stride;
- to_png_rgba (output->buf, rects[i].x2 - rects[i].x1,
- rects[i].y2 - rects[i].y1,
- byte_stride, (guint32*)subdata);
-
- len = output->buf->len - image_start;
-
- overwrite_uint32 (output, size_start, len);
- }
-
- free (rects);
-}
-
-void
-broadway_output_surface_flush (BroadwayOutput *output,
- int id)
-{
- write_header (output, BROADWAY_OP_FLUSH);
- append_uint16 (output, id);
-}
#include <glib.h>
#include <gio/gio.h>
#include "broadway-protocol.h"
+#include "broadway-buffer.h"
typedef struct BroadwayOutput BroadwayOutput;
void broadway_output_set_transient_for (BroadwayOutput *output,
int id,
int parent_id);
-void broadway_output_put_rgb (BroadwayOutput *output,
+void broadway_output_put_buffer (BroadwayOutput *output,
int id,
- int x,
- int y,
- int w,
- int h,
- int byte_stride,
- void *data);
-void broadway_output_put_rgba (BroadwayOutput *output,
- int id,
- int x,
- int y,
- int w,
- int h,
- int byte_stride,
- void *data);
-void broadway_output_surface_flush (BroadwayOutput *output,
- int id);
+ BroadwayBuffer *prev_buffer,
+ BroadwayBuffer *buffer);
void broadway_output_grab_pointer (BroadwayOutput *output,
int id,
gboolean owner_event);
BROADWAY_OP_MOVE_RESIZE = 'm',
BROADWAY_OP_SET_TRANSIENT_FOR = 'p',
BROADWAY_OP_PUT_RGB = 'i',
- BROADWAY_OP_FLUSH = 'f',
BROADWAY_OP_REQUEST_AUTH = 'l',
BROADWAY_OP_AUTH_OK = 'L',
BROADWAY_OP_DISCONNECTED = 'D',
+ BROADWAY_OP_PUT_BUFFER = 'b',
} BroadwayOpType;
typedef struct {
gint32 width;
gint32 height;
gboolean is_temp;
- gboolean last_synced;
gboolean visible;
gint32 transient_for;
- cairo_surface_t *last_surface;
+ BroadwayBuffer *buffer;
+ gboolean buffer_synced;
char *cached_surface_name;
cairo_surface_t *cached_surface;
gint id,
cairo_surface_t *surface)
{
- cairo_t *cr;
BroadwayWindow *window;
+ BroadwayBuffer *buffer;
if (surface == NULL)
return;
if (window == NULL)
return;
- if (window->last_surface == NULL)
- window->last_surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
- window->width,
- window->height);
-
- g_assert (window->width == cairo_image_surface_get_width (window->last_surface));
g_assert (window->width == cairo_image_surface_get_width (surface));
- g_assert (window->height == cairo_image_surface_get_height (window->last_surface));
g_assert (window->height == cairo_image_surface_get_height (surface));
+ buffer = broadway_buffer_create (window->width, window->height,
+ cairo_image_surface_get_data (surface),
+ cairo_image_surface_get_stride (surface));
+
if (server->output != NULL)
{
- window->last_synced = TRUE;
- broadway_output_put_rgba (server->output, window->id, 0, 0,
- cairo_image_surface_get_width (surface),
- cairo_image_surface_get_height (surface),
- cairo_image_surface_get_stride (surface),
- cairo_image_surface_get_data (surface));
-
- broadway_output_surface_flush (server->output, window->id);
+ window->buffer_synced = TRUE;
+ broadway_output_put_buffer (server->output, window->id,
+ window->buffer, buffer);
}
- cr = cairo_create (window->last_surface);
- cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
- cairo_set_source_surface (cr, surface, 0, 0);
- cairo_paint (cr);
- cairo_destroy (cr);
+ if (window->buffer)
+ broadway_buffer_destroy (window->buffer);
+
+ window->buffer = buffer;
}
gboolean
BroadwayWindow *window;
gboolean with_resize;
gboolean sent = FALSE;
- cairo_t *cr;
window = g_hash_table_lookup (server->id_ht,
GINT_TO_POINTER (id));
window->width = width;
window->height = height;
- if (with_resize && window->last_surface != NULL)
- {
- cairo_surface_t *old;
-
- old = window->last_surface;
-
- window->last_surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
- width, height);
-
-
- cr = cairo_create (window->last_surface);
- cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
- cairo_set_source_surface (cr, old, 0, 0);
- cairo_paint (cr);
- cairo_destroy (cr);
-
- cairo_surface_destroy (old);
- }
-
if (server->output != NULL)
{
broadway_output_move_resize_surface (server->output,
if (window->id == 0)
continue; /* Skip root */
- window->last_synced = FALSE;
+ window->buffer_synced = FALSE;
broadway_output_new_surface (server->output,
window->id,
window->x,
{
broadway_output_show_surface (server->output, window->id);
- if (window->last_surface != NULL)
+ if (window->buffer != NULL)
{
- window->last_synced = TRUE;
- broadway_output_put_rgba (server->output, window->id, 0, 0,
- cairo_image_surface_get_width (window->last_surface),
- cairo_image_surface_get_height (window->last_surface),
- cairo_image_surface_get_stride (window->last_surface),
- cairo_image_surface_get_data (window->last_surface));
+ window->buffer_synced = TRUE;
+ broadway_output_put_buffer (server->output, window->id,
+ NULL, window->buffer);
}
- broadway_output_surface_flush (server->output, window->id);
}
}
return 0;
}
-function flushSurface(surface)
-{
- var commands = surface.drawQueue;
- surface.queue = [];
- var context = surface.canvas.getContext("2d");
- context.globalCompositeOperation = "copy";
- var i = 0;
- for (i = 0; i < commands.length; i++) {
- var cmd = commands[i];
- switch (cmd.op) {
- case 'i': // put image data surface
- context.drawImage(cmd.img, cmd.x, cmd.y);
- break;
-
- default:
- alert("Unknown drawing op " + cmd.op);
- }
- }
-}
-
function sendConfigureNotify(surface)
{
sendInput("w", [surface.id, surface.x, surface.y, surface.width, surface.height]);
{
var surface = { id: id, x: x, y:y, width: width, height: height, isTemp: isTemp };
surface.positioned = isTemp;
- surface.drawQueue = [];
surface.transientParent = 0;
surface.visible = false;
+ surface.imageData = null;
var canvas = document.createElement("canvas");
canvas.width = width;
toplevelElement.style["top"] = surface.y + "px";
toplevelElement.style["display"] = "inline";
- toplevelElement.style["visibility"] = "none";
+ toplevelElement.style["visibility"] = "hidden";
surfaces[id] = surface;
stackingOrder.push(surface);
surface.height = h;
}
- /* Flush any outstanding draw ops before (possibly) changing size */
- flushSurface(surface);
-
if (has_size)
resizeCanvas(surface.canvas, w, h);
sendConfigureNotify(surface);
}
-function cmdFlushSurface(id)
+function copyRect(src, srcX, srcY, dest, destX, destY, width, height)
{
- flushSurface(surfaces[id]);
+ // Clip to src
+ if (srcX + width > src.width)
+ width = src.width - srcX;
+ if (srcY + height > src.height)
+ height = src.height - srcY;
+
+ // Clip to dest
+ if (destX + width > dest.width)
+ width = dest.width - destX;
+ if (destY + height > dest.height)
+ height = dest.height - destY;
+
+ var srcRect = src.width * 4 * srcY + srcX * 4;
+ var destRect = dest.width * 4 * destY + destX * 4;
+
+ for (var i = 0; i < height; i++) {
+ var line = src.data.subarray(srcRect, srcRect + width *4);
+ dest.data.set(line, destRect);
+ srcRect += src.width * 4;
+ destRect += dest.width * 4;
+ }
+}
+
+function cmdPutBuffer(id, w, h, data)
+{
+ var surface = surfaces[id];
+ var context = surface.canvas.getContext("2d");
+ var imageData = context.createImageData(w, h);
+ var oldData = surface.imageData;
+ var i, j;
+
+ if (oldData != null) {
+ // Copy old frame into new buffer
+ copyRect(oldData, 0, 0, imageData, 0, 0, oldData.width, oldData.height);
+ }
+
+ var src = 0;
+ var dest = 0;
+
+ //log("put buffer " + w + "x" + h + " size: " + data.length);
+
+ while (src < data.length) {
+ var b = data[src++];
+ var g = data[src++];
+ var r = data[src++];
+ var alpha = data[src++];
+ var len;
+
+ if (alpha != 0) {
+ imageData.data[dest++] = r;
+ imageData.data[dest++] = g;
+ imageData.data[dest++] = b;
+ imageData.data[dest++] = alpha;
+ } else {
+ var cmd = r & 0xf0;
+ switch (cmd) {
+ case 0x00: // Transparent pixel
+ //log("Got transparent");
+ imageData.data[dest++] = 0;
+ imageData.data[dest++] = 0;
+ imageData.data[dest++] = 0;
+ imageData.data[dest++] = 0;
+ break;
+
+ case 0x10: // Delta 0 run
+ len = (r & 0xf) << 16 | g << 8 | b;
+ //log("Got delta0, len: " + len);
+ dest += len * 4;
+ break;
+
+ case 0x20: // Block reference
+ var blockid = (r & 0xf) << 16 | g << 8 | b;
+
+ var block_stride = (oldData.width + 32 - 1) / 32 | 0;
+ var srcY = (blockid / block_stride | 0) * 32;
+ var srcX = (blockid % block_stride | 0) * 32;
+
+ b = data[src++];
+ g = data[src++];
+ r = data[src++];
+ alpha = data[src++];
+
+ var destX = alpha << 8 | r;
+ var destY = g << 8 | b;
+
+ copyRect(oldData, srcX, srcY, imageData, destX, destY, 32, 32);
+
+ //log("Got block, id: " + blockid + "(" + srcX +"," + srcY + ") at " + destX + "," + destY);
+
+ break;
+
+ case 0x30: // Color run
+ len = (r & 0xf) << 16 | g << 8 | b;
+ //log("Got color run, len: " + len);
+
+ b = data[src++];
+ g = data[src++];
+ r = data[src++];
+ alpha = data[src++];
+
+ for (i = 0; i < len; i++) {
+ imageData.data[dest++] = r;
+ imageData.data[dest++] = g;
+ imageData.data[dest++] = b;
+ imageData.data[dest++] = alpha;
+ }
+ break;
+
+ case 0x40: // Delta run
+ len = (r & 0xf) << 16 | g << 8 | b;
+ //log("Got delta run, len: " + len);
+
+ b = data[src++];
+ g = data[src++];
+ r = data[src++];
+ alpha = data[src++];
+
+ for (i = 0; i < len; i++) {
+ imageData.data[dest] = (imageData.data[dest] + r) & 0xff;
+ dest++;
+ imageData.data[dest] = (imageData.data[dest] + g) & 0xff;
+ dest++;
+ imageData.data[dest] = (imageData.data[dest] + b) & 0xff;
+ dest++;
+ imageData.data[dest] = (imageData.data[dest] + alpha) & 0xff;
+ dest++;
+ }
+ break;
+
+ default:
+ alert("Unknown buffer commend " + cmd);
+ }
+ }
+ }
+
+ context.putImageData(imageData, 0, 0);
+ surface.imageData = imageData;
}
function cmdGrabPointer(id, ownerEvents)
cmdMoveResizeSurface(id, has_pos, x, y, has_size, w, h);
break;
- case 'i': // Put image data surface
- q = new Object();
- q.op = 'i';
- q.id = cmd.get_16();
- q.x = cmd.get_16();
- q.y = cmd.get_16();
- var url = cmd.get_image_url ();
- q.img = new Image();
- q.img.src = url;
- surfaces[q.id].drawQueue.push(q);
- if (!q.img.complete) {
- q.img.onload = function() { cmd.free_image_url (url); handleOutstanding(); };
- return false;
- }
- cmd.free_image_url (url);
- break;
-
- case 'b': // Copy rects
- q = new Object();
- q.op = 'b';
- q.id = cmd.get_16();
- var nrects = cmd.get_16();
-
- q.rects = [];
- for (var r = 0; r < nrects; r++) {
- var rect = new Object();
- rect.x = cmd.get_16();
- rect.y = cmd.get_16();
- rect.w = cmd.get_16();
- rect.h = cmd.get_16();
- q.rects.push (rect);
- }
-
- q.dx = cmd.get_16s();
- q.dy = cmd.get_16s();
- surfaces[q.id].drawQueue.push(q);
- break;
-
- case 'f': // Flush surface
+ case 'b': // Put image buffer
id = cmd.get_16();
-
- cmdFlushSurface(id);
- break;
+ w = cmd.get_16();
+ h = cmd.get_16();
+ var data = cmd.get_data();
+ cmdPutBuffer(id, w, h, data);
+ break;
case 'g': // Grab
id = cmd.get_16();
this.pos = this.pos + 4;
return v;
};
-BinCommands.prototype.get_image_url = function() {
+BinCommands.prototype.get_data = function() {
var size = this.get_32();
- var png_blob = new Blob ([new Uint8Array (this.arraybuffer, this.pos, size)], {type:"image/png"});
- var url;
- if (window.webkitURL)
- url = window.webkitURL.createObjectURL(png_blob);
- else
- url = window.URL.createObjectURL(png_blob, {oneTimeOnly: true});
+ var data = new Uint8Array (this.arraybuffer, this.pos, size);
this.pos = this.pos + size;
- return url;
-};
-BinCommands.prototype.free_image_url = function(url) {
- URL.revokeObjectURL(url);
+ return data;
};
function handleMessage(message)